package fun.stgoder.easydarwin.jobs.bl;

import fun.stgoder.easydarwin.jobs.bl.entity.Job;
import fun.stgoder.easydarwin.jobs.bl.model.Pusher1;
import fun.stgoder.easydarwin.jobs.comm.Constants;
import fun.stgoder.easydarwin.jobs.comm.model.Page;
import fun.stgoder.easydarwin.jobs.comm.model.Param;
import fun.stgoder.easydarwin.jobs.comm.recorder.Recorder;
import fun.stgoder.easydarwin.jobs.comm.util.IDUtil;
import fun.stgoder.easydarwin.jobs.comm.util.MediaUtil;
import fun.stgoder.easydarwin.jobs.comm.util.SqlUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public class BL {
    private static final Logger logger = LoggerFactory.getLogger(BL.class);
    private static final DateFormat df = new SimpleDateFormat("yyyyMMdd");

    public static class JOB {

        private static final Logger logger = LoggerFactory.getLogger(JOB.class);

        public static String addJob(String streamId, long lstartTime, long lendTime) throws Exception { // stream id as path

            if (lendTime - lstartTime < Constants.RECORD_MIN_MS)
                throw new Exception("record min time: " + Constants.RECORD_MIN_MS);

            if (lendTime - lstartTime > Constants.RECORD_MAX_MS)
                throw new Exception("record max time: " + Constants.RECORD_MAX_MS);

            if (StringUtils.isBlank(streamId))
                throw new Exception("path required");


            File pusherDir = new File(Constants.EASYDARWIN_M3U8_PATH + File.separator + streamId);
            if (Constants.PRE_RECORD) {
                if (!pusherDir.exists())
                    throw new Exception("pusher path not exist");
            } else {
                pusherDir.mkdirs();
            }

            File pusherRecordDir = new File(Constants.RECORD_DIR + File.separator + streamId);
            if (!pusherRecordDir.exists())
                pusherRecordDir.mkdirs();

            if (Constants.PRE_RECORD) {
                List<String> streamIds = API.streamIds();
                if (!streamIds.contains(streamId))
                    throw new Exception("stream not exist");
            }

            Job job = new Job();
            String id = IDUtil.gen(lstartTime);
            job.setId(id);
            job.setType(1);
            job.setLstartTime(lstartTime);
            job.setLendTime(lendTime);
            job.setStatus(0);
            job.setStreamId(streamId);
            job.setSts(0);
            job.setEts(0);

            File jobRecordDir = new File(pusherRecordDir.getPath() + File.separator + id);
            if (!jobRecordDir.exists())
                jobRecordDir.mkdirs();

            Pusher1 stream = SqlUtil.selectOne("select * from stream  where streamId = :streamId", new Param("streamId", streamId), Pusher1.class);
            if (stream == null)
                throw new Exception("stream not exist");

            if (!Constants.PRE_RECORD) {
                Recorder.startAndPut(id, streamId, stream.getRtsp(), true, lendTime);
            }

            SqlUtil.insert(
                    "insert into job(id, type, lstartTime, lendTime, status, streamId, sts, ets, realDuration, length) values(:id, :type, :lstartTime, :lendTime, :status, :streamId, :sts, :ets, :realDuration, :length)",
                    new Param("id", job.getId()).add("type", job.getType()).add("lstartTime", job.getLstartTime())
                            .add("lendTime", job.getLendTime()).add("status", job.getStatus())
                            .add("streamId", job.getStreamId()).add("sts", job.getSts()).add("ets", job.getEts()).add("realDuration", 0).add("length", 0));

            return id;
        }

        public static void stopJobAndMergeFile(String id) throws Exception {

            if (StringUtils.isBlank(id))
                throw new Exception("job id required");

            Job job = SqlUtil.selectOne(
                    "select " + Job.BASE_COLS + " from job where id = :id",
                    new Param("id", id), Job.class);
            if (job == null)
                throw new Exception("job: " + id + " not exist");

            stopJobAndMergeFile(job);
        }

        public static void stopJobAndMergeFile(Job job) throws Exception {

            if (job.getStatus() != 0)
                throw new Exception("job status: " + job.getStatus());

            if (!Constants.PRE_RECORD)
                try {
                    Recorder.cleanupAndRemove(job.getId());
                } catch (Exception e) {
                    e.printStackTrace();
                }

            SqlUtil.update("update job set status = :status, sts = :sts where id = :id",
                    new Param("status", 1).add("sts", System.currentTimeMillis()).add("id", job.getId()));

            try {
                // merge ts files
                BL.mergeTsFiles(job);

                SqlUtil.update("update job set status = :status, ets = :ets, realDuration = :realDuration, length = :length where id = :id",
                        new Param("status", 2).add("ets", System.currentTimeMillis()).add("realDuration", job.getRealDuration()).add("length", job.getLength()).add("id", job.getId()));
            } catch (Exception e) {
                logger.error("stop job: " + job.getId() + " failed, update status -> -1");
                SqlUtil.update("update job set status = :status, ets = :ets where id = :id",
                        new Param("status", -1).add("ets", System.currentTimeMillis()).add("id", job.getId()));
                throw e;
            }
        }

        public static Job getJob(String id) throws Exception {
            Job job = SqlUtil.selectOne(
                    "select " + Job.BASE_COLS + " from job where id = :id",
                    new Param("id", id), Job.class);
            if (job == null)
                throw new Exception("job: " + id + " not exist");
            return job;
        }

        public static List<Job> getActiveJobs() {
            return SqlUtil.select(
                    "select " + Job.BASE_COLS + " from job where status = 0 or status = 1",
                    Job.class);
        }

        public static Page<Job> getJobs(int page, int pageSize) {
            long total = SqlUtil.count("select count(*) from job");
            List<Job> datas = SqlUtil.select(
                    "select " + Job.BASE_COLS + " from job order by lstartTime desc limit :from,:pageSize",
                    new Param("from", (page - 1) * pageSize).add("pageSize", pageSize), Job.class);

            Page<Job> pageData = new Page<Job>(page, pageSize, total, datas);
            return pageData;
        }

        public static void deleteJob(String id) throws Exception {

            Job job = SqlUtil.selectOne(
                    "select " + Job.BASE_COLS + " from job where id = :id",
                    new Param("id", id), Job.class);

            if (job == null)
                throw new Exception("job: " + id + " not exist");

            if (job.getStatus() == 1)
                throw new Exception("job status: " + job.getStatus());

            if (!Constants.PRE_RECORD)
                try {
                    Recorder.cleanupAndRemove(id);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            SqlUtil.delete("delete from job where id = :id", new Param("id", id));

            // try to clean record file
            String recordFileDirPath = Constants.RECORD_DIR + File.separator + job.getStreamId() +
                    File.separator + job.getId();
            File recordFileDir = new File(
                    recordFileDirPath);

            if (recordFileDir.exists())
                FileUtils.deleteDirectory(recordFileDir);
        }

        public static boolean streamHasActiveJobs(String path) {
            List<Job> jobs = SqlUtil.select(
                    "select " + Job.BASE_COLS + " from job where path = :path and (status = 0 or status = 1)",
                    new Param("path", path), Job.class);
            if (jobs.size() > 0)
                return true;
            return false;
        }
    }

    public static void mergeTsFiles(Job job) throws Exception {
        long lstart = System.currentTimeMillis();

        File tsPath;
        if (Constants.PRE_RECORD) {
            tsPath = BL.getCurrentWorkingM3u8Dir(job.getStreamId());
        } else {
            tsPath = new File(Constants.RECORD_DIR + File.separator + job.getStreamId()
                    + File.separator + job.getId());
            try {
                if (!tsPath.exists())
                    tsPath.mkdirs();
                Recorder.cleanupAndRemove(job.getId());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        long now = System.currentTimeMillis();

        List<File> tsFiles = new ArrayList<>();
        File[] files = tsPath.listFiles();
        for (File file : files) {
            String filename = file.getName();
            if (file.isFile() && filename.endsWith(Constants.RECORD_FILE_SUFFIX)) {
                if (Constants.PRE_RECORD) {
                    long lastModified = file.lastModified();
                    if (Constants.TEST_MODE)
                        System.out.println(new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(lastModified)) + "---" + file.getName());
                    if (lastModified >= (job.getLstartTime() - Constants.MIN_PRE_RECORD_SECOND * 1000) && lastModified <= now) {
                        if (Constants.TEST_MODE) {
                            System.out.println(new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(lastModified)) + "---" + file.getName());
                        }
                        tsFiles.add(file);
                    }
                } else {
                    tsFiles.add(file);
                }
            }
        }

        Collections.sort(tsFiles, new Comparator<File>() {
            public int compare(File f1, File f2) {
                long diff = f1.lastModified() - f2.lastModified();
                if (diff > 0)
                    return 1;
                else if (diff == 0)
                    return 0;
                else
                    return -1;
            }

            public boolean equals(Object obj) {
                return true;
            }
        });

        String recordDirPath = Constants.RECORD_DIR + File.separator + job.getStreamId()
                + File.separator + job.getId();
        File recordDir = new File(recordDirPath);
        if (!recordDir.exists())
            recordDir.mkdirs();

        // record ts file
        String recordFilePath = recordDirPath + File.separator + job.getId() + Constants.RECORD_FILE_SUFFIX;
        File recordFile = new File(recordFilePath);

        // http m3u8 playlist file
        File m3u8PlaylistFile = new File(recordDirPath + File.separator + "index" + Constants.RECORD_PLAYLIST_FILE_SUFFIX);

        FileChannel recordFileChannel = null;
        try (FileOutputStream recordFileOutput = new FileOutputStream(recordFile, true)) {

            recordFileChannel = recordFileOutput.getChannel();

            for (File ts : tsFiles) {
                FileChannel tsFileChannel = null;
                try (FileInputStream tsFileInput = new FileInputStream(ts)) {
                    tsFileChannel = tsFileInput.getChannel();
                    recordFileChannel.transferFrom(tsFileChannel, recordFileChannel.size(), tsFileChannel.size());
                } finally {
                    if (tsFileChannel != null)
                        tsFileChannel.close();
                }
            }

            job.setLength(recordFile.length());

            // make http m3u8 playlist file
            List<String> m3u8Lines = new ArrayList<>();
            m3u8Lines.add("#EXTM3U");
            m3u8Lines.add("#EXT-X-VERSION:3");
            m3u8Lines.add("#EXT-X-TARGETDURATION:" + (Constants.TS_DURATION_SECOND * tsFiles.size()));
            m3u8Lines.add("#EXT-X-MEDIA-SEQUENCE:0");
            String durationText = MediaUtil.getVideoDurationText(recordFile.getPath());
            float realDuration = MediaUtil.getVideoDuration(durationText);
            m3u8Lines.add("#EXTINF:" + durationText + ",");
            m3u8Lines.add(recordFile.getName());
            m3u8Lines.add("#EXT-X-ENDLIST");
            FileUtils.writeLines(m3u8PlaylistFile, m3u8Lines, false);

            job.setRealDuration(realDuration);

            // make mp4??
            if (Constants.COVERT_RECORD_TO_MP4) {
                try {
                    MediaUtil.convertTsToMp4(recordDirPath, job.getId());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            recordFile.delete();
            throw e;
        } finally {
            if (recordFileChannel != null)
                recordFileChannel.close();
        }

        if (Constants.TEST_MODE)
            logger.info("job: " + job.getId() + ", merge files cost: " + (System.currentTimeMillis() - lstart));

    }

    public static File getCurrentWorkingM3u8Dir(String streamId) { // date dirs

        File streamM3u8Dir = new File(Constants.EASYDARWIN_M3U8_PATH + File.separator + streamId);

        long latest = 0;
        File currentDir = null;
        for (File dir : streamM3u8Dir.listFiles()) {
            if (dir.isDirectory()) {
                try {
                    long time = df.parse(dir.getName()).getTime();
                    if (time > latest) {
                        latest = time;
                        currentDir = dir;
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage());
                }
            }
        }

        if (Constants.TEST_MODE)
            logger.info("current m3u8 dir: " + currentDir.getName());

        return currentDir;
    }

    private BL() {
    }
}
